CS 454/554   Homework 4 :  Learning About Unix Files

 

Assigned on: March 23, 2001        Due on: April 11, 2001

 

Execute the C programs given in the following problems. Observe and Interpret  the results. You will learn about UNIX files by performing the suggested experiments.

 

Some of the UNIX calls used in the following problems are: open(), close(), read(), write(), fopen(),  fread(),  fwrite(), lockf(), fflush(), fork()  and  execl(). Use the on-line help or a UNIX book for further information on UNIX calls.

 

Submission: The answer for each problem should be provided in the following format:

(a) a report of your observations, (b) an explanation of observed results. In addition to answers for individual questions write a cohesive summary of what you have learnt through  the various experiments done as a part of this homework. The summary must be typed on a separate sheet of paper. Staple the pages together in the following order: (I) Cover sheet, (II) Summary sheet(s), (III) the answers for individual problems. The homework must be submitted in the required format, at the beginning of the class period on due date. The late homework will be accepted only during the next class period after the due date, with 10% late penalty.

 

1. Run the following program and observe the output. The objective is to understand how the open files are shared by the child and the parent process.

Note:  There is a file descriptor table for each process. This table is duplicated for the child process. As a result, all open files in the parent process when the fork() is called, are also open in the child process. When a file is opened entries are made in the file descriptor table and  the system table, which is global and not duplicated. Both these entries are linked. The system table entry contains the file pointer, the access mode etc. The open files, associated file pointers and the access modes are shared by the parent and the child processes.

 

#include <stdio.h>

#include <fcntl.h>

main( )

{

            int fp;

            char buff[20];

            int pid;

            fp = open("sample", O_RDONLY);

            pid = fork( );

            if (pid==0)

              {

                        printf("child begins %d\n" , getpid( ));

                        read(fp,buff,10);

                        buff[10] = ‘\0’;

                        printf("child read: ");

                        puts(buff);

                        printf("child exits\n");

              }

            else

              {

                        printf("parent begins %d\n" , getpid( ));

                        read(fp,buff,10);

                        buff[10] = ‘\0’;

                        printf("parent read: ");

                        puts(buff);

                        printf("parent exits\n");

              }

            }

You may want to try other experiments, for example, create the child first and open the same file individually within the parent and the child.               

 

2. Run the following program and observe the file pointer position (printed by the parent process). Note that the child process reads 10 characters from the file before the control goes to the parent process. Is the file pointer position 11? What happened?

Note: The three parameters for lseek( ) are: the file handle, the number of characters to move, and from where to move.

 

#include <stdio.h>

#include <fcntl.h>

main( )

{

            int fp;

            char buff[20];

            int pid;

            fp = open("sample",O_RDONLY);

            pid = fork( );

            if (pid==0)

                {

                        printf("child: file handle is %d\n", lseek(fp,01,1));

                        read(fp,buff,10);

                        buff[10] = ‘\0’;

                        printf("child: file handle is now  %d\n", lseek(fp,01,1)) ;

                }

            else

               {

                        wait(0);

                        printf("parent: file handle is %d\n", lseek(fp,01,1));

               }

}

 

 

 

 

3. The following program uses the high-level operations fopen(), fread() instead of the low-level operations open() and read() . Run the program and explain the results.

Note: The high-level call fread() is buffered , i.e., by default, it reads a block of data into a system buffer. The difference due buffering will be reflected by the  two observations. The block size is depends on the system.

#include <stdio.h>

#include <fcntl.h>

main( )

{

            FILE * fp;

            char buff[20];

            int pid;

            fp = fopen("sample", "r" );

            pid = fork( );

            if (pid==0)

                {

                        printf("child: initial file pointer is %d\n", ftell(fp));

                        fread(buff, sizeof(buff), 1,fp);

                        buff[10] = ‘\0’;

                        printf("child read: %s\n", buff);

                        printf("child: file pointer is %d\n", ftell(fp));

                        printf("child exits\n");

                }

            else

               {

                        wait(0);

                        printf("parent: initial file pointer is %d\n", ftell(fp));

                        fread(buff, sizeof(buff), 1,fp);

                        buff[10] = ‘\0’;

                        printf("parent read: %s\n", buff);

                        printf("parent: file pointer is %d\n", ftell(fp));

                        printf("parent exits\n");

               }

}

 

4. The UNIX uses two file pointers: the logical file pointer and the physical file pointers whose values can be obtained by ftell() and lseek() calls respectively. Design an experiment to observe how these file pointers operate. Suggestion: Write a program where the child process reads 10 characters, the parent process reads 10 characters, and finally the child process reads 10 characters again. Use file with a succession of n  A’s, n B’s, n C’s and n D’s where n is the size of the block used for high-level operations. You may choose to design the experiment differently, if you wish.

 

5. Run the following program. Observe the contents of the file mystery using the cat command. How many times the message is written to the file? Why?

#include <stdio.h>

#include <fcntl.h>

main( )

{

            char *p =  "The Man who mistook his wife for a hat by Oliver Sacks";

            FILE *fp;

            fp = fopen("mystery ",  "w");

            fwrite(p,8,1,fp);

            fork();

 }

           

6. Run the following program. Do you observe any output on the terminal?  Repeat the experiment to observe the results of following changes (to be made one at a time) in the program: (a) include \n in the print statement, (b) insert fflush(stdout) statement after the print statement.

Some information about UNIX:  The terminal output is buffered. Unless the buffer is full, physically flushed by us, or the process terminates the  contents  of the buffer will not be displayed. When exec() is executed, the process is replaced in memory by the one that has been called. The buffers which contain data from the previous process are emptied.

NOTE:  You will also need to create ex2.c. Just create a simple ex2.c with one print statement:

printf("I will do all my homework but ...");

 

#include <stdio.h>

main( )

{

            printf("Will you print this message? ");

            execl("ex2", "ex2", (char *)0);

}

 

7.  Run in the background two processes that write to the same file. Observe that the writes to the file are jumbled up. Fix the problem by using lockf() function.

 

 

 

8. Convert the supplied program to use threads.

 

/* Include Files        */

#include <stdio.h>

 

/* External References             */

extern   int      hello( void );

extern   int      world( void );

 

main( int argc, char *argv[] ) {

      hello();

      world();

      printf( "\n" );

 

      return( 0 );

}

 

/* hello - print the "hello" part.        */

      int

hello( void ) {

      printf( "hello " );

      return( 0 );

}

 

/* world - print the "world" part.            */

      int

world( void ) {

      printf( "world" );

      return( 0 );

}

 

 

The following restrictions apply:

 

      1) One thread will print "hello ", one thread will print "world",

            the main function will print the trailing "\n".

      2) The "world" thread will be created before the "hello" thread.

      3) Both the "hello" and "world" thread must run concurrently.

 

Hints & Tips:

 

1) You must use a synchronization primitive to synchronize execution of the two threads;   hello" 

     and "world".

 

2) You must use a synchronization method to assure that the main thread does not execute until 

     after the "world" thread.

 

3) You should not assume that the first created thread will be executed first, the second created thread will be executed second, and so on, i.e., the order of execution of the created threads may depend on the scheduling algorithm of the system, which may not be FIFO.